#!/usr/bin/python2.4
#
#Copyright 2009 Google Inc.
#
#Licensed under the Apache License, Version 2.0 (the "License");
#you may not use this file except in compliance with the License.
#You may obtain a copy of the License at
#
#     http://www.apache.org/licenses/LICENSE-2.0
#
#Unless required by applicable law or agreed to in writing, software
#distributed under the License is distributed on an "AS IS" BASIS,
#WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
#See the License for the specific language governing permissions and
#limitations under the License.
#

"""Provides handlers for fetching a page from AppEngine hosted website.

This module provides handlers for following types of requests:
    1. viewing pages from published version.
    2. previewing pages from any version.
    3. publishing a version.
    4. deleting a version.
"""

import logging
import re
import urllib
from django.utils import simplejson
from google.appengine.api import users
from google.appengine.ext import webapp
from google.appengine.ext.webapp.util import run_wsgi_app
import constants
import base_request_handler
import db_utils
import id_encryption
import page_generator
import digital_content_wizard_exceptions


class ReqHandler(base_request_handler.BaseRequestHandler):
  """This handler is used to handle all the page and data requests.

  It first checks whether url contains version id. If yes, it serves the pages
  from that particular version. If version id is not present, it serves the
  pages from currently published version. It uses GetPage API to fetch the
  page from db.
  """

  version_id = ''
  version_specified = False
  request_url = ''

  def AnalyzeUrl(self):
    """Analyzes the url and gets the reuqest url and version id.

    Returns:
      Boolean specifying the status of the url.
    """
    # Initialize veriables.
    full_url = self.request.path
    parts = full_url.split('/')
    self.version_id = None
    # If nothing is specified in url, default is main.html from the published
    # version.
    if not parts[1]:
      return False
    self.version_id = self.request.get('version_id')
    self.version_specified = False
    if self.version_id:
      logging.debug('version : ' + self.version_id)
      self.request_url = parts[1]
      self.version_specified = True
    else:
      if len(parts) < 3:
        self.request_url = parts[1]
        self.version_id = None
        self.version_specified = False
      else:
        self.version_specified = True
        version = parts[1]
        self.request_url = parts[2]
        match = re.search(r'^([a-zA-Z]+)$', version)
        if match:
         # This is a version.
          logging.debug('version : ' + version)
          self.version_id = match.group(1).strip()
        else:
          logging.error('Invalid version specified')
          self.version_id = None
    return True

  def WriteResponse(self, unquoted_url, content):
    """Writes back response.

    Args:
      unquoted_url: String representing current url in unquoted form.
      content: HTML content of the requested page. A String.
    """
    if content:
      parts = unquoted_url.split('.')
      content_type = parts[len(parts)-1]
      if  content_type == 'js':
        logging.debug('javascript request')
        self.response.headers['Content-Type'] = 'application/x-javascript'
      elif content_type == 'css':
        logging.debug('css request')
        self.response.headers['Content-Type'] = 'text/css'
      self.response.out.write(content)
    else:
      self.error(404)

  def GetAncestors(self, node, url):
    """Returns the array of nodes on the path from ROOT of page tree.

    This list is needed as json metadata doesn't maintain the parent pointers.
    So instead of browsing through page tree again and again, all the
    ancestors of the nodes are returned.

    Args:
      node: JSON object representing page tree node.
      url: String representing the requested url.
    Returns:
      The array of ancestor nodes in the form of dictionary objects.
    """
    if node[self.encoded_url_string] == url:
      return [node]
    if self.encoded_child_nodes_string not in node:
      return None
    child_nodes = node[self.encoded_child_nodes_string]
    for child_node in child_nodes:
      descendents = self.GetAncestors(child_node, url)
      if descendents:
        descendents.append(node)
        return descendents
    return []

  def IsLandingPage(self, url):
    """Returns whether the requested page is a landing page.

    Args:
      url: Url string.
    Returns:
      True if the requested page is a landing page; False otherwise.
    """
    db_handler = db_utils.DbHandler()
    metadata = db_handler.GetMetadataById(self.version_id, url.split('.')[0])
    if not metadata:
      return False
    return True

  def GetContent(self, url):
    """Tries to get content from db or generates it if not present.

    Args:
      url: String representing request url.
    Returns:
      HTML content. A string.
    """
    db_handler = db_utils.DbHandler()
    content = db_handler.GetPage(url, self.version_id)
    if not content:
      logging.debug('Page not found in db. Trying to generate the page')
      if self.IsLandingPage(url):
        # Landing Page.
        logging.debug('This is a landing page request')
        page_generator_instance = page_generator.LandingPageGenerator()
        ancestors = None
      else:
        # Level page.
        logging.debug('This is a level page request')
        page_tree_blob = db_handler.GetPageTree(self.version_id)
        values = simplejson.loads(page_tree_blob)
        page_generator_instance = page_generator.LevelPageGenerator(values[0])
        self.encoded_child_nodes_string = 'childNodes'.encode(
            constants.ENCODING)
        self.encoded_url_string = 'url'.encode(constants.ENCODING)
        ancestors = self.GetAncestors(values[0], url)
        if not ancestors:
          logging.warning('Possibly an invalid URL request')
          return None
      unquoted_url = urllib.unquote(urllib.unquote(self.request.url))
      url_unicode = unicode(unquoted_url, constants.ENCODING, errors='strict')
      try:
        content = page_generator_instance.GeneratePage(url_unicode,
                                                       self.version_id,
                                                       ancestors)
      except digital_content_wizard_exceptions.InvalidContentTypeException, e:
        page_generator_instance = None
        return None
      page_generator_instance = None
    return content

  def get(self):
    """GET method for this request handler."""
    db_handler = db_utils.DbHandler()
    url_status = self.AnalyzeUrl()
    if not url_status:
      content = db_handler.GetPage('main.html')
      self.response.out.write(content)
      return

    unquoted_url = urllib.unquote(urllib.unquote(self.request_url))

    url_unicode = unicode(unquoted_url, constants.ENCODING, errors='strict')
    logging.debug('URL requested:')
    logging.debug(url_unicode)
    if self.version_specified:
      decrypted_version_id = id_encryption.Decrypt(self.version_id)
      self.version_id = decrypted_version_id
      if not decrypted_version_id:
        logging.error('Invalid version id specified')
        self.WriteResponse(unquoted_url, None)
        return
    else:
      # Get the published version.
      logging.debug('Version not specified')
      self.version_id = db_handler.GetPublishedVersion()
    content = self.GetContent(url_unicode)
    self.WriteResponse(unquoted_url, content)


class PreviewHandler(webapp.RequestHandler):
  """Handles preview request.

  This handler handles the request /[a-zA-Z]+/. It simple redirects this request
  to /specified_version_id/main.html page.
  """

  def get(self):
    """GET method for this request handler."""
    # Get the page url.
    logging.debug('in Preview')
    if self.request.url.endswith('/'):
      self.redirect(self.request.url + 'main.html')
    else:
      self.redirect(self.request.url + '/main.html')
    return


class ConfirmHandler(webapp.RequestHandler):
  """Handler for publishing the specified version.

  This class provides handler to publish the specified version. It uses
  PublishVersion API of DbHandler.
  """

  def get(self):
    """GET method for this request handler."""
    db_handler = db_utils.DbHandler()
    version_id_str = self.request.get('version_id')
    decrypted_version_id = id_encryption.Decrypt(version_id_str)
    status = False
    if decrypted_version_id:
      (status, version_id) = db_handler.PublishVersion(decrypted_version_id)
    else:
      logging.error('Invalid version id specified')
      return
    if not status:
      logging.error('Unable to publish version ', version_id)
    self.redirect('/admin')


class DeletePagesHandler(base_request_handler.BaseRequestHandler):
  """Handler to delete a version.

  This handler deletes all metadata and pages of the version specified as URL
  parameter. This handler works iteratively on subsets of metadata and pages.
  It redirects requests to itself and goes on recursively until all metadata
  and pages of this version are deleted.
  """

  def get(self):
    """GET method for this request handler."""
    version_id = self.request.get('version_id')
    self._Handler(version_id)

  def post(self):
    """POST method for this request handler."""
    version_id = self.request.get('version_id')
    self._Handler(version_id)

  def _Handler(self, version_id):
    """GET and POST methods for this request handler."""
    try:
      decrypted_version_id = id_encryption.Decrypt(version_id)
      template_values = {}
      if decrypted_version_id:
        db_handler = db_utils.DbHandler()
        status = db_handler.RemoveVersion(decrypted_version_id)
        if status:
          template_values['redirect_url'] = '/admin'
        else:
          template_values['redirect_url'] = '/delete?version_id=' + version_id
        self.response.out.write(self.GenerateHtml('redirection.html',
                                                  template_values))
    except digital_content_wizard_exceptions.DigitalContentWizardException, e:
      redirect_url = '/admin'
      logging.error(e)
      self.response.out.write(self.GenerateHtml('error.html',
                                                {'message': str(e),
                                                 'redirect_url':
                                                 redirect_url}))


application = webapp.WSGIApplication(
    [('/delete', DeletePagesHandler),
     ('/confirm', ConfirmHandler),
     ('/[a-zA-Z]+/?', PreviewHandler),
     ('/', ReqHandler),
     ('/*.*', ReqHandler)],
    debug=True)


def main():
  run_wsgi_app(application)


if __name__ == '__main__':
  main()
